import { DateTime } from './DateTime';
import { type DateDiffResult, type TimeType } from '../types';

/**
 * 日期时间计算器类
 * @class DateTimeCalculator
 * @description 用于计算当前 DateTime 实例与指定时间之间的差值
 */
export class DateTimeCalculator {
  /**
   * 目标时间的 DateTime 实例
   * @private
   */
  private __targetTime!: DateTime;

  /**
   * 当前 DateTime 实例
   * @private
   */
  private readonly __sourceTime: DateTime;

  /**
   * 构造函数
   * @param {DateTime} sourceTime - 源 DateTime 实例（当前时间）
   */
  constructor(sourceTime: DateTime) {
    this.__sourceTime = sourceTime;
  }

  /**
   * 设置目标时间
   * @param {TimeType} targetTime - 目标时间，支持时间戳、字符串、Date对象、dayjs实例和DateTime实例
   * @returns {DateTimeCalculator} 当前实例，用于链式调用
   */
  public target(targetTime: TimeType): DateTimeCalculator {
    this.__targetTime = new DateTime(targetTime);
    return this;
  }

  /**
   * 获取目标时间实例
   * @private
   * @param {TimeType} $target - 可选的目标时间参数，支持时间戳、字符串、Date对象、dayjs实例和DateTime实例
   * @returns {DateTime} 目标时间的DateTime实例
   * @throws {Error} 如果目标时间未设置
   */
  private __getTarget($target?: TimeType): DateTime {
    if ($target) {
      return new DateTime($target);
    } else {
      if (this.__targetTime) {
        return this.__targetTime;
      } else {
        throw new Error('目标时间未设置');
      }
    }
  }

  /**
   * 计算两个时间的天数差
   * @param {TimeType} target - 目标时间，支持时间戳、字符串、Date对象、dayjs实例和DateTime实例
   * @returns {number} 天数差值，正数表示目标时间在源时间之后，负数表示在之前
   */
  public daysDiff(target?: TimeType): number {
    let startDayjs = this.__sourceTime.dayjs();
    let endDayjs = this.__getTarget(target).dayjs();

    // 直接计算天数差值
    return endDayjs.diff(startDayjs, 'day');
  }

  /**
   * 添加一个基于DateDiffResult的时间增量
   * @param {DateDiffResult} diff - 时间差值对象，包含年、月、日、时、分、秒、毫秒的增量
   * @returns {DateTime} 新的DateTime实例
   * @throws {Error} 如果发生超过一次的进位或退位
   */
  public add(diff: DateDiffResult): DateTime {
    // 获取源时间的dayjs实例
    let resultDayjs = this.__sourceTime.dayjs().clone();
    const direction = diff.isNegative ? -1 : 1;
    
    // 检查是否需要多次进位或退位的辅助函数
    const checkSingleCarry = (value: number, max: number, unitName: string): [number, number] => {
      let carry = 0;
      let remainder = value;
      
      // 处理正方向（进位）
      if (remainder >= max) {
        carry = Math.floor(remainder / max);
        remainder = remainder % max;
        // 检查是否需要多次进位
        if (carry > 1) {
          throw new Error(`超过一次进位：${unitName}需要进位${carry}次`);
        }
      }
      // 处理负方向（退位）
      else if (remainder < 0) {
        carry = Math.floor(remainder / max) - 1;
        remainder = remainder % max + max;
        // 检查是否需要多次退位
        if (carry < -1) {
          throw new Error(`超过一次退位：${unitName}需要退位${Math.abs(carry)}次`);
        }
      }
      
      return [remainder, carry];
    };
    
    // 从低到高处理时间单位，只允许一次进位或退位
    
    // 1. 处理毫秒
    let milliseconds = resultDayjs.millisecond() + diff.milliseconds * direction;
    let [finalMilliseconds, secondsCarry] = checkSingleCarry(milliseconds, 1000, '毫秒');
    
    // 2. 处理秒
    let seconds = resultDayjs.second() + diff.seconds * direction + secondsCarry;
    let [finalSeconds, minutesCarry] = checkSingleCarry(seconds, 60, '秒');
    
    // 3. 处理分钟
    let minutes = resultDayjs.minute() + diff.minutes * direction + minutesCarry;
    let [finalMinutes, hoursCarry] = checkSingleCarry(minutes, 60, '分钟');
    
    // 4. 处理小时
    let hours = resultDayjs.hour() + diff.hours * direction + hoursCarry;
    let [finalHours, daysCarry] = checkSingleCarry(hours, 24, '小时');
    
    // 设置时分秒毫秒
    resultDayjs = resultDayjs
      .set('hour', finalHours)
      .set('minute', finalMinutes)
      .set('second', finalSeconds)
      .set('millisecond', finalMilliseconds);
    
    // 5. 处理天数、月份和年份
    // 注意：对于天数，我们只允许来自小时的一次进位/退位，不允许diff.days本身导致多次进位
    const daysTotal = diff.days * direction + daysCarry;
    
    // 检查天数是否会导致多次进位/退位
    if (Math.abs(daysTotal) > 1) {
      // 如果diff.days不为零且daysCarry也不为零，说明是组合进位，需要特别处理
      if (diff.days !== 0 && daysCarry !== 0) {
        throw new Error('天数与小时进位组合导致超过一次进位/退位');
      }
    }
    
    if (daysTotal !== 0) {
      resultDayjs = resultDayjs.add(daysTotal, 'day');
    }
    
    // 对于月份和年份，由于它们的进制不固定（28-31天/月），我们直接使用dayjs.add
    // 但仍然检查是否会导致多次进位/退位
    if (diff.months !== 0) {
      if (Math.abs(diff.months) > 1) {
        throw new Error(`月份增量过大：${diff.months}个月，超过一次进位/退位限制`);
      }
      resultDayjs = resultDayjs.add(diff.months * direction, 'month');
    }
    
    if (diff.years !== 0) {
      if (Math.abs(diff.years) > 1) {
        throw new Error(`年份增量过大：${diff.years}年，超过一次进位/退位限制`);
      }
      resultDayjs = resultDayjs.add(diff.years * direction, 'year');
    }
    
    // 返回新的DateTime实例
    return new DateTime(resultDayjs);
  }

  /**
   * 计算两个时间的完整差值
   * @param {TimeType} target - 目标时间，支持时间戳、字符串、Date对象、dayjs实例和DateTime实例
   * @returns {DateDiffResult} 包含年、月、日、时、分、秒、毫秒的差值对象
   */
  public diff(target?: TimeType): DateDiffResult {
    let startDatetime = this.__sourceTime;
    let startDayjs = startDatetime.dayjs();
    let endDatetime = new DateTime(this.__getTarget(target));
    let endDayjs = endDatetime.dayjs();

    // 处理负时间差
    const isNegative = endDayjs.isBefore(startDayjs);
    if (isNegative) {
      [startDayjs, endDayjs] = [endDayjs, startDayjs];
    }

    // 初始化差值结果
    let years = 0;
    let months = 0;
    let days = 0;
    let hours = 0;
    let minutes = 0;
    let seconds = 0;
    let milliseconds = 0;

    // 创建临时日期用于计算
    let tempDate = startDayjs.clone();

    // 先计算年
    while (true) {
      // 直接设置年份进行比较
      const testYear = startDayjs.year() + years + 1;
      const testDate = startDayjs.set('year', testYear);

      if (testDate.isBefore(endDayjs)) {
        years++;
      } else if (testDate.isAfter(endDayjs)) {
        // 找到正确的年份后，更新tempDate
        tempDate = startDayjs.set('year', startDayjs.year() + years);
        break;
      } else {
        years++;
        tempDate = startDayjs.set('year', startDayjs.year() + years);
        break;
      }
    }

    // 再计算月
    while (true) {
      const testMonth = tempDate.month() + months + 1;
      const testDate = tempDate.set('month', testMonth);

      if (testDate.isBefore(endDayjs)) {
        months++;
      } else if (testDate.isAfter(endDayjs)) {
        tempDate = tempDate.set('month', tempDate.month() + months);
        break;
      } else {
        months++;
        tempDate = tempDate.set('month', tempDate.month() + months);
        break;
      }
    }

    // 计算日
    while (true) {
      const testDay = tempDate.date() + days + 1;
      const testDate = tempDate.set('date', testDay);

      if (testDate.isBefore(endDayjs)) {
        days++;
      } else if (testDate.isAfter(endDayjs)) {
        tempDate = tempDate.set('date', tempDate.date() + days);
        break;
      } else {
        days++;
        tempDate = tempDate.set('date', tempDate.date() + days);
        break;
      }
    }

    // 计算小时
    while (true) {
      const testHour = tempDate.hour() + hours + 1;
      const testDate = tempDate.set('hour', testHour);

      if (testDate.isBefore(endDayjs)) {
        hours++;
      } else if (testDate.isAfter(endDayjs)) {
        tempDate = tempDate.set('hour', tempDate.hour() + hours);
        break;
      } else {
        hours++;
        tempDate = tempDate.set('hour', tempDate.hour() + hours);
        break;
      }
    }

    // 计算分钟
    while (true) {
      const testMinute = tempDate.minute() + minutes + 1;
      const testDate = tempDate.set('minute', testMinute);

      if (testDate.isBefore(endDayjs)) {
        minutes++;
      } else if (testDate.isAfter(endDayjs)) {
        tempDate = tempDate.set('minute', tempDate.minute() + minutes);
        break;
      } else {
        minutes++;
        tempDate = tempDate.set('minute', tempDate.minute() + minutes);
        break;
      }
    }

    // 计算秒
    while (true) {
      const testSecond = tempDate.second() + seconds + 1;
      const testDate = tempDate.set('second', testSecond);

      if (testDate.isBefore(endDayjs)) {
        seconds++;
      } else if (testDate.isAfter(endDayjs)) {
        tempDate = tempDate.set('second', tempDate.second() + seconds);
        break;
      } else {
        seconds++;
        tempDate = tempDate.set('second', tempDate.second() + seconds);
        break;
      }
    }

    // 计算毫秒差值
    milliseconds = endDayjs.diff(tempDate, 'millisecond');
    // 确保所有时间字段都是正数，只通过isNegative标志表示整体正负方向
    return {
      isNegative: isNegative,
      years: years,
      months: months,
      days: days,
      hours: hours,
      minutes: minutes,
      seconds: seconds,
      milliseconds: milliseconds,
    };
  }

}